#include "QtNode.hpp"

#include <QtCore/QObject>

#include <utility>
#include <iostream>

#include "QtFlowScene.hpp"

#include "QtNodeGraphicsObject.hpp"
#include "QtNodeDataModel.hpp"

#include "QtConnectionGraphicsObject.hpp"
#include "QtStyle.hpp"
#include "QDialog"
#include "QPainter"
#include "cmath"
namespace Qt
{
	NodeGeometry::
		NodeGeometry(std::unique_ptr<QtNodeDataModel> const& dataModel)
		: _width(100)
		, _height(150)
		, _inputPortWidth(70)
		, _outputPortWidth(70)
		, _entryHeight(20)
		, _spacing(20)
		, _captionHeightSpacing(5)
		, _hovered(false)
		, _nSources(dataModel->nPorts(PortType::Out))
		, _nSinks(dataModel->nPorts(PortType::In))
		, _draggingPos(-1000, -1000)
		, _dataModel(dataModel)
		, _fontMetrics(QFont())
		, _boldFontMetrics(QFont())
		, TipsWidget(new QDialog)
		, TipsWidget_(new QDockWidget)
		, PortTipsWidget(new QDialog)
		, isPortTipsShow(new bool(false))
		, isNodeTipsShow(new bool(false))
	{
		QFont f; f.setBold(true);
		//this->InitText();
		_boldFontMetrics = QFontMetrics(f);

		auto renderButton = std::make_shared<HotkeyButton>();
		renderButton->setIconsPath(":Render_Default.svg", ":Render_Checked.svg", ":Render_Hover.svg", ":Render_Checked.svg");
		renderButton->setMode(Qt::HotkeyButton::HotkeyMode::checkedMode);
		renderButton->setUserPolicy([=]()->bool { return _dataModel->hotKeyRenderChecked(); });
		this->pushHotkeyButton(renderButton);
		
		auto activeButton = std::make_shared<HotkeyButton>();
		activeButton->setIconsPath(":Sim_Default.svg", ":Sim_Checked.svg", ":Sim_Hover.svg", ":Sim_Default.svg");
		activeButton->setMode(Qt::HotkeyButton::HotkeyMode::checkedMode);
		activeButton->setUserPolicy([=]()->bool { return _dataModel->hotKeySimChecked(); });
		this->pushHotkeyButton(activeButton);

		auto autoSyncButton = std::make_shared<HotkeyButton>();
		autoSyncButton->setIconsPath(":Auto_Default.svg", ":Auto_Checked.svg", ":Auto_Hover.svg", ":Auto_Checked.svg");
		autoSyncButton->setMode(Qt::HotkeyButton::HotkeyMode::checkedMode);
		autoSyncButton->setUserPolicy([=]()->bool { return _dataModel->hotKeyAutoSyncChecked(); });
		this->pushHotkeyButton(autoSyncButton);

		auto resetButton = std::make_shared<HotkeyButton>();
		resetButton->setIconsPath(":Reset_Default.svg", ":Reset_Default.svg", ":Reset_Hover.svg", ":Reset_Click.svg");
		resetButton->setMode(Qt::HotkeyButton::HotkeyMode::clickMode);
		this->pushHotkeyButton(resetButton);

		auto gpuButton = std::make_shared<HotkeyButton>();
		gpuButton->setIconsPath(":GPU_Default.svg", ":GPU_Default.svg", ":GPU_Hover.svg", ":GPU_Default.svg");
		gpuButton->setMode(Qt::HotkeyButton::HotkeyMode::clickMode);
		this->pushHotkeyButton(gpuButton);


		
	}

	NodeGeometry::~NodeGeometry() 
	{
		HideTips();
		HidePortTips();
	}

	void NodeGeometry::recalculateHotkeyButtons() const
	{

		NodeStyle const& nodeStyle = _dataModel->nodeStyle();
		float diam = nodeStyle.ConnectionPointDiameter;

		unsigned int captionHeight = this->captionHeight();

		QSize _buttonSize = QSize( captionHeight + diam, captionHeight+ diam);
		QSize iconSize = 0.8 * _buttonSize;

		unsigned int buttonOriginX = this->width() + diam;
		unsigned int buttonOriginY = this->height() - captionHeight;

		for (size_t i = 0; i < buttonNum; i++)
		{
			int offset = _buttonSize.width();

			buttonOriginX -= offset * mHotKeyButtons[i]->scaleX;
			QPointF origin = QPointF(buttonOriginX, buttonOriginY);
			mHotKeyButtons[i]->setTransform(origin, _buttonSize.width(), _buttonSize.height());
			
		}
	}

	QSize NodeGeometry::getHotkeyButtonsSize() const
	{
		int sizeX = 0;
		int sizeY = 0;
		
		for (auto button : mHotKeyButtons)
		{
			sizeX += button->getSize().width() * button->scaleX;
			sizeY += button->getSize().height();
		}

		return QSize(sizeX,sizeY);
	}

	void NodeGeometry::ShowTips()const
	{
		*isNodeTipsShow = true;

		QLabel* TipsLabel = new QLabel;
		TipsLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
		TipsLabel->setParent(TipsWidget);
		TipsLabel->setText(_dataModel->nodeTips());
		TipsLabel->setWordWrap(true);
		TipsLabel->setFixedWidth(200);
		TipsLabel->adjustSize();
		TipsLabel->setStyleSheet("QLabel{color:white;background-color:#346792;border: 1px solid #000000;border-radius:3px; padding: 0px;}");

		TipsWidget->setWindowFlags(Qt::WindowTransparentForInput | Qt::ToolTip );
		TipsWidget->move(QCursor().pos().x()+20, QCursor().pos().y()+20);
		if (!_dataModel->nodeTips().isEmpty())
		{
			TipsWidget->show();
		}
	}

	void NodeGeometry::HideTips()const
	{
		*isNodeTipsShow = false;
		TipsWidget->hide();
		delete TipsWidget->findChild<QLabel*>();
	}

	bool NodeGeometry::getPortTipsState()const { return *isPortTipsShow; }

	void NodeGeometry::ShowPortTips()const
	{
		if (!*isPortTipsShow && ! *isNodeTipsShow)
		{
			*isPortTipsShow = true;
			
			QLabel* PortTipsLabel = new QLabel;
			PortTipsLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
			PortTipsLabel->setParent(PortTipsWidget);
			PortTipsLabel->setText(_dataModel->portTips(hoverport_type, hoverport_id));
			PortTipsLabel->setWordWrap(true);
			
			//PortTipsLabel->setFixedWidth(200);
			PortTipsLabel->adjustSize();
			PortTipsLabel->setStyleSheet("QLabel{color:white;background-color:#346792;border: 1px solid #000000;border-radius:3px ; padding: 0px;}");
			PortTipsWidget->setWindowFlags(Qt::WindowTransparentForInput | Qt::ToolTip);

			PortTipsWidget->move(QCursor().pos().x() + 20, QCursor().pos().y() + 20);

			if (!_dataModel->portTips(hoverport_type,hoverport_id).isEmpty())
			{
				PortTipsWidget->show();
			}

		}

	}


	void NodeGeometry::HidePortTips()const
	{
		if (*isPortTipsShow)
			*isPortTipsShow = false;
			PortTipsWidget->hide();
			delete PortTipsWidget->findChild<QLabel*>();
	}

	unsigned int
		NodeGeometry::nSources() const
	{
		return _dataModel->nPorts(PortType::Out);
	}

	unsigned int
		NodeGeometry::nSinks() const
	{
		return _dataModel->nPorts(PortType::In);
	}




	QRectF
		NodeGeometry::
		entryBoundingRect() const
	{
		double const addon = 0.0;

		return QRectF(0 - addon,
			0 - addon,
			_entryWidth + 2 * addon,
			_entryHeight + 2 * addon);
	}


	QRectF
		NodeGeometry::
		boundingRect() const
	{
		auto const &nodeStyle = StyleCollection::nodeStyle();

		double addon = 4 * nodeStyle.ConnectionPointDiameter;

		return QRectF(0 - addon,
			0 - addon,
			_width + 2 * addon,
			_height + 2 * addon);
	}


	void
		NodeGeometry::
		recalculateSize() const
	{
		_entryHeight = _fontMetrics.height();

		{
			unsigned int maxNumOfEntries = _dataModel->allowExported() ? _nSources - 1 : _nSources;
			maxNumOfEntries = std::max(_nSinks, maxNumOfEntries);
			
			unsigned int step = _entryHeight + _spacing;
			_height = step * maxNumOfEntries;

		}

		if (auto w = _dataModel->embeddedWidget())
		{
			_height = std::max(_height, static_cast<unsigned>(w->height()));
		}

		_height += captionHeight();

		_inputPortWidth = portWidth(PortType::In);
		_outputPortWidth = portWidth(PortType::Out);

		_width = _inputPortWidth +
			_outputPortWidth
			+2 * _spacing;
			

		if (auto w = _dataModel->embeddedWidget())
		{
			_width += w->width();
		}

		_width = std::max(_width, captionWidth());

		if (_dataModel->validationState() != NodeValidationState::Valid)
		{
			_width = std::max(_width, validationWidth());
			_height += validationHeight() + _spacing;
		}

		unsigned int buttonsWidth = getHotkeyButtonsSize().width();
		_width = std::max(_width, buttonsWidth);

		if (_dataModel->hotkeyEnabled()) 
		{
			_height += statusBarHeight() + _spacing;
			recalculateHotkeyButtons();
		}


	}


	void
		NodeGeometry::
		recalculateSize(QFont const & font) const
	{
		QFontMetrics fontMetrics(font);
		QFont boldFont = font;

		boldFont.setBold(true);

		QFontMetrics boldFontMetrics(boldFont);

		if (_boldFontMetrics != boldFontMetrics)
		{
			_fontMetrics = fontMetrics;
			_boldFontMetrics = boldFontMetrics;



			recalculateSize();
		}
	}


	QPointF
		NodeGeometry::
		portScenePosition(PortIndex index,
			PortType portType,
			QTransform const & t) const
	{
		auto const &nodeStyle = StyleCollection::nodeStyle();

		unsigned int step = _entryHeight + _spacing;

		QPointF result;

		double totalHeight = captionHeightSpacing();

		totalHeight += captionHeight();

		totalHeight += step * index;

		// TODO: why?
		totalHeight += step / 2.0;

		switch (portType)
		{
		case PortType::Out:
		{
			double x = _width + nodeStyle.ConnectionPointDiameter;
			result = _dataModel->allowExported() ? QPointF(x, totalHeight - captionHeight() - captionHeightSpacing() - step / 3.0) : QPointF(x, totalHeight);
			break;
		}

		case PortType::In:
		{
			double x = 0.0 - nodeStyle.ConnectionPointDiameter;
			result = _dataModel->allowImported() ? QPointF(x, totalHeight - captionHeight() - captionHeightSpacing() - step / 3.0) : QPointF(x, totalHeight);
			break;
		}

		default:
			break;
		}

		return t.map(result);
	}


	PortIndex
		NodeGeometry::
		checkHitScenePoint(PortType portType,
			QPointF const scenePoint,
			QTransform const & sceneTransform) const
	{
		auto const &nodeStyle = StyleCollection::nodeStyle();

		PortIndex result = INVALID_PORT;

		if (portType == PortType::None)
			return result;

		double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter;

		unsigned int const nItems = _dataModel->nPorts(portType);

		for (unsigned int i = 0; i < nItems; ++i)
		{
			auto pp = portScenePosition(i, portType, sceneTransform);

			QPointF p = pp - scenePoint;
			auto    distance = std::sqrt(QPointF::dotProduct(p, p));

			if (distance < tolerance)
			{
				result = PortIndex(i);
				break;
			}
		}

		return result;
	}


	PortIndex
		NodeGeometry::
		hoverHitScenePoint(PortType portType,
			QPointF const scenePoint,
			QTransform const& sceneTransform) const
	{
		auto const& nodeStyle = StyleCollection::nodeStyle();

		PortIndex result = INVALID_PORT;

		if (portType == PortType::None)
			return result;

		double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter;

		unsigned int const nItems = _dataModel->nPorts(portType);

		for (unsigned int i = 0; i < nItems; ++i)
		{
			auto pp = portScenePosition(i, portType, sceneTransform);

			QPointF p = pp - scenePoint;
			auto    distance = std::sqrt(QPointF::dotProduct(p, p));

			if (distance < tolerance)
			{
				result = PortIndex(i);
				break;
			}
		}

		return result;
	}



	PortIndex
		NodeGeometry::
		hoverHitPortArea(PortType portType,
			QPointF const scenePoint,
			QTransform const& sceneTransform,
			NodeGeometry const& geom,
			QtNodeDataModel const* model) const
	{
		auto const& nodeStyle = StyleCollection::nodeStyle();

		QFont font;
		font.setFamily("none");
		QFontMetrics metrics(font);

		PortIndex result = INVALID_PORT;

		if (portType == PortType::None)
			return result;

		QPointF p1;
		QPointF p2;

		unsigned int const nItems = _dataModel->nPorts(portType);
		
			//std::string type;
			for (size_t i = 0; i < nItems; ++i)
			{
				p1 = geom.portScenePosition(i, portType, sceneTransform);
				QPointF pt = QPointF(geom.portScenePosition(i, portType, sceneTransform));
					//if (portType == PortType::None)
					//{
					//	type = "None";
					//}
					//else if (portType == PortType::In)
					//{
					//	type = "In";
					//}
					//else if (portType == PortType::Out)
					//{
					//	type = "Out";
					//}
				QString s;

					if (model->portCaptionVisible(portType, (PortIndex)i))
					{
						s = model->portCaption(portType, (PortIndex)i);
					}
					else
					{
						s = model->dataType(portType, (PortIndex)i).name;
					}

				auto rect = metrics.boundingRect(s);

				p2.setY(p1.y() + rect.height() /4);
				p1.setY(p1.y() - rect.height()* 3/5);
				
					switch (portType)
					{
					case PortType::In:
						p1.setX(0 + sceneTransform.dx());
						p2.setX(rect.width()+ sceneTransform.dx());
						break;

					case PortType::Out:
						p1.setX(geom.width() - rect.width() + sceneTransform.dx());
						p2.setX(geom.width() + sceneTransform.dx());
						break;

					default:
						break;
					}
					//std::cout << type <<":" << i << "--" << "p1\B5\C4λ\D6\C3   x=" << p1.x() << "   y=" << p1.y() << std::endl;
					//std::cout << type <<":" << i << "--" << "p2\B5\C4λ\D6\C3   x=" << p2.x() << "   y=" << p2.y() << std::endl;

					if (scenePoint.x()> p1.x() && scenePoint.x() < p2.x() && scenePoint.y() >p1.y() && scenePoint.y() < p2.y())
					{
						//std::cout <<"\B7\B5\BB\D8ֵ\A3\BA" << type << i << std::endl;
						//std::cout << "********************* <  \D2ѷ\B5\BB\D8  > *********************"  << std::endl;
						return PortIndex(i);
						//std::cout << "********************* <  break  > *********************" << std::endl;
						break;
					}

			}
		return result = INVALID_PORT;
	}

	PortIndex
		NodeGeometry::
		findHitPort(PortType portType,
			QPointF const scenePoint,
			QTransform const& sceneTransform,
			NodeGeometry const& geom,
			QtNodeDataModel const* model) const 
	{
		PortIndex result = INVALID_PORT;
		PortType _portType = portType;
		QPointF const _scenePoint = scenePoint;
		QTransform const _sceneTransform = sceneTransform;
		NodeGeometry const _geom = geom;
		QtNodeDataModel const* _model = model;

		result = geom.hoverHitPortArea(_portType, _scenePoint, _sceneTransform, _geom, _model);
		//std::cout << "result1=" << result << std::endl;
			if (result == INVALID_PORT)
			{
				result = geom.hoverHitScenePoint(_portType, _scenePoint, _sceneTransform);
				//std::cout << "result2=" << result << std::endl;
			}

		return result;

	}


	QRect
		NodeGeometry::
		resizeRect() const
	{
		unsigned int rectSize = 7;

		return QRect(_width - rectSize,
			_height - rectSize,
			rectSize,
			rectSize);
	}


	QPointF
		NodeGeometry::
		widgetPosition() const
	{
		if (auto w = _dataModel->embeddedWidget())
		{
			if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag)
			{
				// If the widget wants to use as much vertical space as possible, place it immediately after the caption.
				return QPointF(_spacing + portWidth(PortType::In), captionHeight());
			}
			else
			{
				if (_dataModel->validationState() != NodeValidationState::Valid)
				{
					return QPointF(_spacing + portWidth(PortType::In),
						(captionHeight() + _height - validationHeight() - _spacing - w->height()) / 2.0);
				}

				return QPointF(_spacing + portWidth(PortType::In),
					(captionHeight() + _height - w->height()) / 2.0);
			}
		}
		return QPointF();
	}

	int
		NodeGeometry::
		equivalentWidgetHeight() const
	{
		if (_dataModel->validationState() != NodeValidationState::Valid)
		{
			return height() - captionHeight() + validationHeight();
		}

		return height() - captionHeight();
	}

	unsigned int
		NodeGeometry::
		captionHeight() const
	{
		if (!_dataModel->captionVisible())
			return 0;

		QString name = _dataModel->caption();

		return _boldFontMetrics.boundingRect(name).height() + captionHeightSpacing();
	}

	unsigned int
		NodeGeometry::
		statusBarHeight() const 
	{
		return captionHeight();
	}

	int NodeGeometry::updateHotkeyHover( QPointF point) const
	{
		int selectId = -1;
		for (size_t i = 0; i < mHotKeyButtons.size(); i++)
		{
			auto button = mHotKeyButtons[i];
			if (button->mouseHover(point))
				selectId = i;
		}
		return selectId;
	}

	int NodeGeometry::updateMousePress(QPointF point) const
	{
		for (size_t i = 0; i < mHotKeyButtons.size(); i++)
		{
			auto button = mHotKeyButtons[i];
			if(button->mouseClick(point))
				return int(i);
		}
		return int(-1);
	}
	int NodeGeometry::updateMouseRelease(QPointF point) const
	{
		for (size_t i = 0; i < mHotKeyButtons.size(); i++)
		{
			auto button = mHotKeyButtons[i];
			if(button->mouseRelease(point))
				return int(i);
		}
		return int(-1);
	}

	unsigned int
		NodeGeometry::
		captionWidth() const
	{
		if (!_dataModel->captionVisible())
			return 0;

		QString name = _dataModel->caption();

		unsigned int w = _dataModel->hotkeyEnabled() ?  2 * captionHeight() + _boldFontMetrics.boundingRect(name).width()
			: 2 * captionHeight() + _boldFontMetrics.boundingRect(name).width();

		return w;
	}


	unsigned int
		NodeGeometry::
		validationHeight() const
	{
		QString msg = _dataModel->validationMessage();

		return _boldFontMetrics.boundingRect(msg).height();
	}


	unsigned int
		NodeGeometry::
		validationWidth() const
	{
		QString msg = _dataModel->validationMessage();

		return _boldFontMetrics.boundingRect(msg).width();
	}


	QPointF
		NodeGeometry::
		calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, QtNode* targetNode,
			PortIndex sourcePortIndex, PortType sourcePort, QtNode* sourceNode,
			QtNode& newNode)
	{
		//Calculating the nodes position in the scene. It'll be positioned half way between the two ports that it "connects". 
		//The first line calculates the halfway point between the ports (node position + port position on the node for both nodes averaged).
		//The second line offsets this coordinate with the size of the new node, so that the new nodes center falls on the originally
		//calculated coordinate, instead of it's upper left corner.
		auto converterNodePos = (sourceNode->nodeGraphicsObject().pos() + sourceNode->nodeGeometry().portScenePosition(sourcePortIndex, sourcePort) +
			targetNode->nodeGraphicsObject().pos() + targetNode->nodeGeometry().portScenePosition(targetPortIndex, targetPort)) / 2.0f;
		converterNodePos.setX(converterNodePos.x() - newNode.nodeGeometry().width() / 2.0f);
		converterNodePos.setY(converterNodePos.y() - newNode.nodeGeometry().height() / 2.0f);
		return converterNodePos;
	}


	unsigned int
		NodeGeometry::
		portWidth(PortType portType) const
	{
		unsigned width = 0;

		for (auto i = 0ul; i < _dataModel->nPorts(portType); ++i)
		{
			QString name;

			if (_dataModel->portCaptionVisible(portType, i))
			{
				name = _dataModel->portCaption(portType, i);
			}
			else
			{
				name = _dataModel->dataType(portType, i).name;
			}

			width = std::max(unsigned(_fontMetrics.boundingRect(name).width()),
				width);
		}

		return width;
	}

	NodeState::
		NodeState(std::unique_ptr<QtNodeDataModel> const &model)
		: _inConnections(model->nPorts(PortType::In))
		, _outConnections(model->nPorts(PortType::Out))
		, _reaction(NOT_REACTING)
		, _reactingPortType(PortType::None)
		, _resizing(false)
	{}


	std::vector<NodeState::ConnectionPtrSet> const &
		NodeState::
		getEntries(PortType portType) const
	{
		if (portType == PortType::In)
			return _inConnections;
		else
			return _outConnections;
	}


	std::vector<NodeState::ConnectionPtrSet> &
		NodeState::
		getEntries(PortType portType)
	{
		if (portType == PortType::In)
			return _inConnections;
		else
			return _outConnections;
	}


	NodeState::ConnectionPtrSet
		NodeState::
		connections(PortType portType, PortIndex portIndex) const
	{
		auto const &connections = getEntries(portType);

		return connections[portIndex];
	}


	void
		NodeState::
		setConnection(PortType portType,
			PortIndex portIndex,
			QtConnection& connection)
	{
		auto &connections = getEntries(portType);

		connections.at(portIndex).insert(std::make_pair(connection.id(),
			&connection));
	}


	void
		NodeState::
		eraseConnection(PortType portType,
			PortIndex portIndex,
			QUuid id)
	{
		getEntries(portType)[portIndex].erase(id);
	}


	NodeState::ReactToConnectionState
		NodeState::
		reaction() const
	{
		return _reaction;
	}


	PortType
		NodeState::
		reactingPortType() const
	{
		return _reactingPortType;
	}


	NodeDataType
		NodeState::
		reactingDataType() const
	{
		return _reactingDataType;
	}


	void
		NodeState::
		setReaction(ReactToConnectionState reaction,
			PortType reactingPortType,
			NodeDataType reactingDataType)
	{
		_reaction = reaction;

		_reactingPortType = reactingPortType;

		_reactingDataType = std::move(reactingDataType);
	}


	bool
		NodeState::
		isReacting() const
	{
		return _reaction == REACTING;
	}


	void
		NodeState::
		setResizing(bool resizing)
	{
		_resizing = resizing;
	}


	bool
		NodeState::
		resizing() const
	{
		return _resizing;
	}

	QtNode::
		QtNode(std::unique_ptr<QtNodeDataModel> && dataModel)
		: _uid(QUuid::createUuid())
		, _nodeDataModel(std::move(dataModel))
		, _nodeState(_nodeDataModel)
		, _nodeGeometry(_nodeDataModel)
		, _nodeGraphicsObject(nullptr)
	{
		_nodeGeometry.recalculateSize();

		// propagate data: model => node
		connect(_nodeDataModel.get(), &QtNodeDataModel::dataUpdated,
			this, &QtNode::onDataUpdated);

		connect(_nodeDataModel.get(), &QtNodeDataModel::embeddedWidgetSizeUpdated,
			this, &QtNode::onNodeSizeUpdated);
	}


	QtNode::
		~QtNode() = default;

	QJsonObject
		QtNode::
		save() const
	{
		QJsonObject nodeJson;

		nodeJson["id"] = _uid.toString();

		nodeJson["model"] = _nodeDataModel->save();

		QJsonObject obj;
		obj["x"] = _nodeGraphicsObject->pos().x();
		obj["y"] = _nodeGraphicsObject->pos().y();
		nodeJson["position"] = obj;

		return nodeJson;
	}


	void
		QtNode::
		restore(QJsonObject const& json)
	{
		_uid = QUuid(json["id"].toString());

		QJsonObject positionJson = json["position"].toObject();
		QPointF     point(positionJson["x"].toDouble(),
			positionJson["y"].toDouble());
		_nodeGraphicsObject->setPos(point);

		_nodeDataModel->restore(json["model"].toObject());
	}


	QUuid
		QtNode::
		id() const
	{
		return _uid;
	}


	void
		QtNode::
		reactToPossibleConnection(PortType reactingPortType,
			NodeDataType const &reactingDataType,
			QPointF const &scenePoint)
	{
		QTransform const t = _nodeGraphicsObject->sceneTransform();

		QPointF p = t.inverted().map(scenePoint);

		_nodeGeometry.setDraggingPosition(p);

		_nodeGraphicsObject->update();

		_nodeState.setReaction(NodeState::REACTING,
			reactingPortType,
			reactingDataType);
	}


	void
		QtNode::
		resetReactionToConnection()
	{
		_nodeState.setReaction(NodeState::NOT_REACTING);
		_nodeGraphicsObject->update();
	}


	QtNodeGraphicsObject const &
		QtNode::
		nodeGraphicsObject() const
	{
		return *_nodeGraphicsObject.get();
	}


	QtNodeGraphicsObject &
		QtNode::
		nodeGraphicsObject()
	{
		return *_nodeGraphicsObject.get();
	}


	void
		QtNode::
		setGraphicsObject(std::unique_ptr<QtNodeGraphicsObject>&& graphics)
	{
		_nodeGraphicsObject = std::move(graphics);

		_nodeGeometry.recalculateSize();
	}


	NodeGeometry&
		QtNode::
		nodeGeometry()
	{
		return _nodeGeometry;
	}


	NodeGeometry const&
		QtNode::
		nodeGeometry() const
	{
		return _nodeGeometry;
	}


	NodeState const &
		QtNode::
		nodeState() const
	{
		return _nodeState;
	}


	NodeState &
		QtNode::
		nodeState()
	{
		return _nodeState;
	}


	QtNodeDataModel*
		QtNode::
		nodeDataModel() const
	{
		return _nodeDataModel.get();
	}


	void
		QtNode::
		propagateData(std::shared_ptr<QtNodeData> nodeData,
			PortIndex inPortIndex) const
	{
		_nodeDataModel->setInData(std::move(nodeData), inPortIndex);

		//Recalculate the nodes visuals. A data change can result in the node taking more space than before, so this forces a recalculate+repaint on the affected node
		_nodeGraphicsObject->setGeometryChanged();
		_nodeGeometry.recalculateSize();
		_nodeGraphicsObject->update();
		_nodeGraphicsObject->moveConnections();
	}


	void
		QtNode::
		onDataUpdated(PortIndex index)
	{
		auto nodeData = _nodeDataModel->outData(index);

		auto connections =
			_nodeState.connections(PortType::Out, index);

		for (auto const & c : connections)
			c.second->propagateData(nodeData);
	}

	void
		QtNode::
		onNodeSizeUpdated()
	{
		if (nodeDataModel()->embeddedWidget())
		{
			nodeDataModel()->embeddedWidget()->adjustSize();
		}
		nodeGeometry().recalculateSize();
		for (PortType type : {PortType::In, PortType::Out})
		{
			for (auto& conn_set : nodeState().getEntries(type))
			{
				for (auto& pair : conn_set)
				{
					QtConnection* conn = pair.second;
					conn->getConnectionGraphicsObject().move();
				}
			}
		}
	}
}
